/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Permissions;

using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting {

    public sealed partial class ScriptCode {
    
        /// <summary>
        /// Creates the methods and optimized Scope's which get associated with each ScriptCode.
        /// </summary>
        private Scope CompileOptimizedScope() {
            DlrMainCallTarget target;
            IAttributesCollection globals;
            if (UseLightweightScopes) {
                CompileWithArrayGlobals(out target, out globals);
            } else {
                CompileWithStaticGlobals(out target, out globals);
            }

            // Force creation of names used in other script codes into all optimized dictionaries
            Scope scope = new Scope(globals);
            ((IModuleDictionaryInitialization)globals).InitializeModuleDictionary(new CodeContext(scope, _languageContext));

            // everything succeeded, commit the results
            _optimizedTarget = target;
            _optimizedScope = scope;

            return scope;
        }
        
        private void CompileToDisk(TypeGen/*!*/ typeGen) {

            MethodBuilder mb = typeGen.TypeBuilder.DefineMethod(
                SourceUnit.Path,
                CompilerHelpers.PublicStatic | MethodAttributes.SpecialName,
                typeof(object),
                new Type[] { typeof(CodeContext) }
            );

            ToDiskRewriter rewriter = new ToDiskRewriter();
            LambdaExpression lambda = rewriter.RewriteLambda(_code);

            LambdaCompiler.CompileLambda(lambda, typeGen, mb, EmitDebugSymbols);

            CustomAttributeBuilder cab = new CustomAttributeBuilder(
                typeof(DlrCachedCodeAttribute).GetConstructor(new Type[] { typeof(Type), typeof(string[]) }),
                new object[] {  _languageContext.GetType(), (_optimizedScope != null) ? ArrayUtils.ToArray(rewriter.Names) : null }
            );

            mb.SetCustomAttribute(cab);
        }

        private static bool UseLightweightScopes {        
            get {

                if (ScriptDomainManager.Options.LightweightScopes) {
                    return true;
                }

#if !SILVERLIGHT
                try {
                    // Static field compiler requires ReflectionEmit (in CLR V2) or UnmanagedCode (in CLR V2 SP1) permission.
                    // If we are running in partial-trust, fall through to generated dynamic code.
                    // TODO: Symbol information requires unmanaged code permission.  Move the error
                    // handling lower and just don't dump PDBs.
                    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
                } catch (SecurityException) {
                    return true;
                }
#endif
                return false;
            }
        }

        private void CompileWithArrayGlobals(out DlrMainCallTarget target, out IAttributesCollection globals) {
            GlobalArrayRewriter rewriter = new GlobalArrayRewriter();
            LambdaExpression lambda = rewriter.RewriteLambda(_code, MakeDebugName());

            // Compile target
            target = LambdaCompiler.CompileLambda<DlrMainCallTarget>(lambda, EmitDebugSymbols);

            // Create globals
            globals = rewriter.CreateDictionary();
        }

        private void CompileWithStaticGlobals(out DlrMainCallTarget target, out IAttributesCollection globals) {
            // Create typegen
            TypeGen typeGen = Snippets.Shared.DefineType(MakeDebugName(), typeof(CustomSymbolDictionary), false, false, EmitDebugSymbols);
            typeGen.TypeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

            // Create rewriter
            GlobalStaticFieldRewriter rewriter = new GlobalStaticFieldRewriter(typeGen);

            // Create method for top level lambda
            MethodBuilder mb = typeGen.TypeBuilder.DefineMethod(
                "Initialize",
                CompilerHelpers.PublicStatic,
                typeof(object),
                new Type[] { typeof(CodeContext) }
            );

            // Compile lambda
            LambdaCompiler.CompileLambda(rewriter.RewriteLambda(_code), typeGen, mb, EmitDebugSymbols);
            
            // Create globals dictionary, finish type
            rewriter.EmitDictionary();
            Type type = typeGen.FinishType();
            globals = (IAttributesCollection)Activator.CreateInstance(type);

            // Create target
            target = (DlrMainCallTarget)Delegate.CreateDelegate(typeof(DlrMainCallTarget), type.GetMethod("Initialize"));

            // TODO: clean this up after clarifying dynamic site initialization logic
            DynamicSiteHelpers.InitializeFields(type);
        }
        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private string/*!*/ MakeDebugName() {
#if DEBUG
            if (SourceUnit != null && SourceUnit.HasPath) {
                return "OptScope_" + ReflectionUtils.ToValidTypeName(Path.GetFileNameWithoutExtension(IOUtils.ToValidPath(SourceUnit.Path)));
            }
#endif
            return "S";
        }

    }
}
